package com.me.study.jdk8.stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.me.study.jdk8.lambda.Apple;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Java8：Stream API 测试
 *
 * @author ME
 * @date 2021/4/26
 */
public class TestStreamCollect {

    private ObjectWriter objectWriter = new ObjectMapper().writerWithDefaultPrettyPrinter();
    private static List<Integer> list;
    private static List<Apple> appleList;

    @Test
    public void compareWithForLoop22() {
        System.out.println("============= 循环500000 ============");
        list = Stream.iterate(0, n -> n + 1).limit(500000).collect(Collectors.toList());
        testMethod(list);

        System.out.println("============= 循环5000000 ============");
        list = Stream.iterate(0, n -> n + 1).limit(5000000).collect(Collectors.toList());
        testMethod(list);
    }

    @Test
    public void compareWithForLoop() {
        long l = System.currentTimeMillis();
        loop(o -> {
            List<Apple> collect = appleList.stream().filter(apple -> apple.getWeight() < 200)
                    .peek(apple -> apple.setName(apple.getName() + "2333"))
                    .collect(Collectors.toList());
        });
        System.out.println("stream 耗时：" + (System.currentTimeMillis() - l));

        long l1 = System.currentTimeMillis();
        loop(o -> {
            List<Apple> collect2 = new ArrayList<>();
            for (Apple apple : appleList) {
                if (apple.getWeight() < 200) {
                    apple.setName(apple.getName() + "2333");
                    collect2.add(apple);
                }
            }
        });
        System.out.println("for 耗时：" + (System.currentTimeMillis() - l1));
    }

    @Test
    public void collect() {
        final Set<Apple> set = appleList.stream().map(apple -> apple).collect(Collectors.toSet());

        // Collectors.toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)
        // keyMapper 返回 Map的 key
        // valueMapper 返回 Map的 value
        // mergeFunction 用来决定 key相同时返回的 value
        Map<String, Apple> map = appleList.stream()
                .collect(Collectors.toMap(Apple::getColor, apple -> apple, (apple1, apple2) -> {
                    // mergeFunction 用来选择 key重复时的 元素
                    System.out.println("color 相同的元素：" + apple1.getColor() + " -- " + apple1.getName());
                    System.out.println("color 相同的元素：" + apple2.getColor() + " -- " + apple2.getName());
                    return apple1;
                }));
        map.entrySet().stream().forEach(System.out::println);
    }

    @Test
    public void partitioning() {
        // 根据指定条件分隔成 两组
        Map<Boolean, List<Apple>> part = appleList.stream()
                .collect(Collectors.partitioningBy(apple -> apple.getWeight() > 200));
        part.entrySet().stream().forEach(System.out::println);
    }

    @Test
    public void grouping() throws JsonProcessingException {
        List<Origin> origins = new ArrayList<>();
        // 根据产地分组
        Map<String, List<Apple>> originGroup = appleList.stream()
                .collect(Collectors.groupingBy(Apple::getOrigin, LinkedHashMap::new, Collectors.toList()));
        for (Map.Entry<String, List<Apple>> originEntry : originGroup.entrySet()) {
            List<Apple> originList = originEntry.getValue();
            Origin origin = new Origin(originEntry.getKey());
            origins.add(origin);

            List<Color> colors = new ArrayList<>();
            origin.setColors(colors);

            Map<String, List<Apple>> colorGroup = originList.stream()
                    .collect(Collectors.groupingBy(Apple::getColor, LinkedHashMap::new, Collectors.toList()));
            for (Map.Entry<String, List<Apple>> colorEntry : colorGroup.entrySet()) {
                List<Apple> apples = colorEntry.getValue();
                Color color = new Color(colorEntry.getKey());
                colors.add(color);

                color.setApples(apples);
            }
        }

        System.out.println(objectWriter.writeValueAsString(origins));;
    }

    // TODO 单次循环组装两次分组后的数据
    @Test
    public void grouping2() throws JsonProcessingException {
        List<Origin> origins = new ArrayList<>();
        Map<String, List<Color>> originCache = new HashMap<>();
        Map<String, List<Apple>> colorCache = new HashMap<>();
        // 根据产地分组
        for (Apple apple : appleList) {
            List<Color> colors = originCache.computeIfAbsent(apple.getOrigin(), s -> {
                Origin origin = new Origin(apple.getOrigin());
                origins.add(origin);

                List<Color> colorsTemp = new ArrayList<>();
                origin.setColors(colorsTemp);
                return colorsTemp;
            });

            List<Apple> apples = colorCache.computeIfAbsent(apple.getColor(), s -> {
                Color color = new Color(apple.getColor());
                colors.add(color);

                List<Apple> applesTemp = new ArrayList<>();
                color.setApples(applesTemp);
                return applesTemp;
            });
            apples.add(apple);
        }

        System.out.println(objectWriter.writeValueAsString(origins));;
    }

    /**
     * 并行排序不影响结果
     */
    @Test
    public void sorted() {
        List<String> expected = Arrays.asList("green:青苹果", "red:红富士", "red:蛇果", "yellow:金帅");

        System.out.println("颜色自然倒序排序：");
        List<String> sorted = appleList.stream().sorted(Comparator.comparing(Apple::getColor).reversed()).map(apple -> {
                return apple.getColor() + ":" + apple.getName();
            }).collect(Collectors.toList());
        // TODO 标准写法
        List<String> sorted2 = appleList.stream().sorted(((Comparator<Apple>)(p1, p2) -> {
                return p1.getColor().compareTo(p2.getColor());
            }).reversed()).map(apple -> {
                return apple.getColor() + ":" + apple.getName();
            }).collect(Collectors.toList());
        System.out.println(sorted);
        System.out.println(sorted2);

        System.out.println("\n先按颜色自然排序，再按重量排序：");
        sorted = appleList.stream().parallel()
                .sorted(Comparator.comparing(Apple::getColor).thenComparingInt(Apple::getWeight))
                .map(apple -> apple.getColor() + ":" + apple.getName()).collect(Collectors.toList());
        System.out.println(sorted);

        sorted = appleList.stream().sorted((p1, p2) -> {
            if (!p1.getColor().equals(p2.getColor())) {
                return p1.getColor().compareTo(p2.getColor());
            } else {
                return p1.getWeight() - p2.getWeight();
            }
        }).map(apple -> apple.getColor() + ":" + apple.getName()).collect(Collectors.toList());
        System.out.println(sorted);
    }

    @Test
    public void sorted2() {
        List<String> expected = Arrays.asList("green:青苹果", "red:红富士", "red:蛇果", "yellow:金帅");
        List<String> sorted = appleList.stream().sorted(Comparator.comparing(Apple::getColor)).map(apple -> {
                return apple.getColor() + ":" + apple.getName();
            }).collect(Collectors.toList());
        System.out.println(sorted);
        assertTrue("颜色自然排序",
                sorted.containsAll(expected));
    }

    @Test
    public void reducing() {
        // 将 stream 中的元素用特定的连接符（没有的话，则直接连接）连接成一个字符串
        String reducing = appleList.stream()
                .collect(Collectors.reducing("", Apple::getName, (i, j) -> (i + j)));
        assertEquals("规约",
                "青苹果红富士金帅蛇果", reducing);
    }

    @Test
    public void joining() {
        // 将 stream 中的元素用特定的连接符（没有的话，则直接连接）连接成一个字符串
        String names = appleList.stream().map(Apple::getName).collect(Collectors.joining(", "));
        assertEquals("将元素用特定的连接符（没有的话，则直接连接）连接成字符串",
                "青苹果, 红富士, 金帅, 蛇果", names);
    }

    @Test
    public void count() {
        // Collectors.counting()
        Long count = list.stream().filter(x -> x > 6).collect(Collectors.counting());
        count = list.stream().filter(x -> x > 6).count();
        assertEquals("list中大于6的元素个数：3",
                Long.valueOf(3), count);
    }

    @Test
    public void averaging() {
        // 平均值：averagingInt、averagingLong、averagingDouble
        Double average = list.stream().collect(Collectors.averagingInt(value -> value));
        assertEquals("平均值：",
                Double.valueOf(6D), average);
    }

    @Test
    public void max() {
        // 最值：maxBy、minBy
        Optional<Integer> max = list.stream().collect(Collectors.maxBy(Integer::compare));
        max = list.stream().collect(Collectors.maxBy(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                // -1--小于
                return o1.compareTo(o2);
            }
        }));
        assertEquals("最大值：12",
                Integer.valueOf(12), max.get());
    }

    @Test
    public void sum() {
        // 求和：summingInt、summingLong、summingDouble
        Integer sum = list.stream().collect(Collectors.summingInt(value -> value));
        assertEquals("求和：",
                Integer.valueOf(42), sum);
    }

    @Test
    public void summarizing() {
        // 一次统计 count、平均、max、min、sum：
        // summarizingInt、summarizingLong、summarizingDouble
        DoubleSummaryStatistics collect = list.stream().collect(Collectors.summarizingDouble(value -> value));
        System.out.println(collect);
    }

    @Before
    public void newStream() {
        TestStreamCollect.list = Arrays.asList(7, 6, 9, 3, 12, 4, 1);

        TestStreamCollect.appleList = new ArrayList<>();
        Apple a1 = new Apple("青苹果", "green", 150, "山东");
        Apple a2 = new Apple("红富士", "red", 300, "山东");
        Apple a3 = new Apple("金帅", "yellow", 180, "云南");
        Apple a4 = new Apple("蛇果", "red", 200, "山东");
        appleList.add(a1);
        appleList.add(a2);
        appleList.add(a3);
        appleList.add(a4);
    }

    void loop(Consumer consumer) {
        for (int i = 0; i < 10000; i++) {
            consumer.accept(null);
        }
    }

    /**
     * 测试方法
     */
    void testMethod(List<Integer> list1) {
        Long startTime = System.currentTimeMillis();
        testFor(list1);
        Long endTime = System.currentTimeMillis();
        System.out.println("for: " + (endTime - startTime));

        Long startTime1 = System.currentTimeMillis();
        testForeach(list1);
        Long endTime1 = System.currentTimeMillis();
        System.out.println("增强 for: " + (endTime1 - startTime1));

        Long startTime2 = System.currentTimeMillis();
        testStream(list1);
        Long endTime2 = System.currentTimeMillis();
        System.out.println("forEach: " + (endTime2 - startTime2));
    }

    public void testFor(List<Integer> list){
        for (int i = 0; i < list.size(); i++) {
            list.get(i).toString();
        }
    }

    /**
     *	测试Foreach循环
     */
    public void testForeach(List<Integer> list){
        for (Integer o : list) {
            o.toString();
        }
    }

    /**
     *	测试Stream.foreach循环
     */
    public void testStream(List<Integer> list){
        list.stream().forEach(integer -> integer.toString());
    }

    class Color {
        private String name;
        List<Apple> apples;

        public Color(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<Apple> getApples() {
            return apples;
        }

        public void setApples(List<Apple> apples) {
            this.apples = apples;
        }
    }
    class Origin {
        private String name;
        List<Color> colors;

        public Origin(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<Color> getColors() {
            return colors;
        }

        public void setColors(List<Color> colors) {
            this.colors = colors;
        }
    }
}
